#include "ql_rtos.h"
#include "sockets.h"
#include "wl_transport.h"
#include "wl_mutex.h"
#include "wl_trace.h"


#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

typedef struct __wl_transport_message {
    WL_SOCKET socket;
    wl_int32  event;
} wl_transport_message;

typedef enum {
    WL_TRANSPORT_ADD_CMD,
    WL_TRANSPORT_DEL_CMD,
    WL_TRANSPORT_SET_EVENT_CMD,
    WL_TRANSPORT_END_CMD
}WL_TRANSPORT_CMD;

typedef struct _wl_transport_cmd {
    WL_SOCKET_DESCRIPTOR socket;
    wl_int32 command;
    wl_int32 transport_event;
}wl_transport_cmd;

#define COMMAND_UDP_PORT 8800

#define MAX_SOCK_NUM 16

#define START_OF_SOCKFD 1

#define EPOLL_EVENT_NUM 10

#define CHECK_SOCKET(socket) (START_OF_SOCKFD > socket || MAX_SOCK_NUM < socket) ? wl_false : wl_true

typedef struct __wl_transport {
    wl_bool              inuse;
    WL_SOCKET_DESCRIPTOR fd;
    wl_int32             taskid;
    wl_int32             event;
    wl_int32             type;
    wl_sock_callback     callback;
    void*                context;
} wl_transport;

typedef struct {
    wl_bool      initialized;
    wl_bool      exit;
    wl_int32     count;
    wl_transport transports[MAX_SOCK_NUM];
    WL_MUTEX     mutex;
    ql_task_t    thread;
    int          cmdUdpFd;
} wl_transport_global;

wl_transport_global s_transport_global = {0};

static WL_SOCK_EVENT translate_transport_event_to_socket_event(wl_int32 transport_event) {
    switch (transport_event) {
        case WL_TRANSPORT_EVENT_READ:
            return WL_SOCK_READ_AVAILABLE;
        case WL_TRANSPORT_EVENT_WRITE:
            return WL_SOCK_WRITE_AVAILABLE;
        case WL_TRANSPORT_EVENT_ACCEPT:
            return WL_SOCK_NEW_CONNECTION;
        case WL_TRANSPORT_EVENT_CONNECT:
            return WL_SOCK_CONNECTION_SUCCESS;
        case WL_TRANSPORT_EVENT_CLOSE:
            return WL_SOCK_CONNECTION_LOST;
        case WL_TRANSPORT_EVENT_LOST:
            return WL_SOCK_CONNECTION_LOST;
        case WL_TRANSPORT_EVENT_EXCEPTION:
            return WL_SOCK_CONNECTION_FAILED;
        default:
            break;
    }
    return WL_SOCK_ERROR_UNKNOWN;
}

static wl_int32 wl_transport_send_cmd(WL_SOCKET_DESCRIPTOR socket, wl_int32 transport_cmd, wl_int32 event) {
    if (s_transport_global.cmdUdpFd >= 0) {
        struct sockaddr_in addr_serv;
        wl_transport_cmd cmd;
        int sendBytes;
        addr_serv.sin_family = AF_INET;
        addr_serv.sin_port = htons(COMMAND_UDP_PORT);
        addr_serv.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

        cmd.command = transport_cmd;
        cmd.socket = socket;
        cmd.transport_event = event;
        WLLOGD("send command:%d with event:%d", transport_cmd, event);
        sendBytes = sendto(s_transport_global.cmdUdpFd, (void *)&cmd, sizeof(wl_transport_cmd), 0, (struct sockaddr *)&addr_serv, sizeof(addr_serv));
        if (sendBytes >= 0) {
            return 0;
        }
    }

    return -1;
}

void transport_main_thread(void* param) {
    wl_transport* transport = wl_null;
    WL_TRANSPORT_EVENT transport_event;
    int i;
    fd_set rset;
    fd_set wset;
    int ret = 0;

    while (!s_transport_global.exit) {
        FD_ZERO(&rset);
        FD_ZERO(&wset);

        FD_SET(s_transport_global.cmdUdpFd, &rset);

        for(i = 0; i < MAX_SOCK_NUM; i++) {
            if (s_transport_global.transports[i].inuse) {
                if (s_transport_global.transports[i].event & WL_TRANSPORT_EVENT_WRITE ||
                s_transport_global.transports[i].event & WL_TRANSPORT_EVENT_CONNECT) {
                    FD_SET(s_transport_global.transports[i].fd, &wset);
                }

                if (s_transport_global.transports[i].event & WL_TRANSPORT_EVENT_READ ||
                s_transport_global.transports[i].event & WL_TRANSPORT_EVENT_ACCEPT ||
                s_transport_global.transports[i].event & WL_TRANSPORT_EVENT_CONNECT) {
                    FD_SET(s_transport_global.transports[i].fd, &rset);
                }
            }
        }

        WLLOGD("wait for event:%d %d", *(int *)&rset, *(int *)&wset);
        ret = select(FD_SETSIZE, &rset, &wset, NULL, NULL);

        if (ret < 0) {
            WLLOGW("!!!!!!!!!!!!!!!!! select is error:%d", errno);
        }else if (ret > 0) {           
            if (FD_ISSET(s_transport_global.cmdUdpFd, &rset)) {
                wl_transport_cmd cmd;
                struct sockaddr_in addr_serv;
                int len, recvSize;
                recvSize = recvfrom(s_transport_global.cmdUdpFd, &cmd, sizeof(wl_transport_cmd), 0, (struct sockaddr *)&addr_serv, (socklen_t *)&len);
                if (recvSize > 0) {
                    switch (cmd.command) {
                        case WL_TRANSPORT_ADD_CMD: {
                        }
                        break;

                        case WL_TRANSPORT_DEL_CMD: {
                            if (cmd.socket >= 0) {
                                close(cmd.socket);
                            }
                        }
                        break;

                        case WL_TRANSPORT_SET_EVENT_CMD: {
                        }
                        break;

                        case WL_TRANSPORT_END_CMD: {
                            wl_int32 index;
                            wl_mutex_acquire(&s_transport_global.mutex);
                            for (index = 0; index < MAX_SOCK_NUM; index++) {
                                if (s_transport_global.transports[index].inuse) {
                                    close(s_transport_global.transports[index].fd);
                                    memset(&s_transport_global.transports[index], 0, sizeof(wl_transport));
                                }
                            }

                            close(s_transport_global.cmdUdpFd);
                            wl_mutex_release(&s_transport_global.mutex);
                        }
                        break;
                    }
                }
            }

            for(i = 0; i < MAX_SOCK_NUM; i++) {
                transport_event = WL_TRANSPORT_EVENT_NONE;
                if (s_transport_global.transports[i].inuse) {
                    transport = &s_transport_global.transports[i];
                    wl_mutex_acquire(&s_transport_global.mutex);
                    if (FD_ISSET(transport->fd, &rset)) {
                        if (FD_ISSET(transport->fd, &wset)) {
                            int sock_error;
                            socklen_t len;
                            int ret = getsockopt(transport->fd, SOL_SOCKET, SO_ERROR, &sock_error, &len);

                            WLLOGW("socket is connecting, ret:%d sock_err:%d", ret, sock_error);

                            if (transport->event & WL_TRANSPORT_EVENT_CONNECT) {
                                transport->event &= ~WL_TRANSPORT_EVENT_CONNECT;
                                if (ret == 0 && sock_error == 0) {
                                    transport_event = WL_TRANSPORT_EVENT_CONNECT;
                                }else {
                                    transport_event = WL_TRANSPORT_EVENT_EXCEPTION;
                                }
                            }
                        }else {
                            if (transport->event & WL_TRANSPORT_EVENT_ACCEPT) {
                                transport->event &= ~WL_TRANSPORT_EVENT_ACCEPT;
                                transport_event = WL_TRANSPORT_EVENT_ACCEPT;
                            }else {
                                transport->event &= ~WL_TRANSPORT_EVENT_READ;
                                transport_event = WL_TRANSPORT_EVENT_READ;
                            }
                        }
                    }else if (FD_ISSET(transport->fd, &wset)) {
                        if (transport->event & WL_TRANSPORT_EVENT_CONNECT) {
                            transport->event &= ~WL_TRANSPORT_EVENT_CONNECT;
                            transport_event = WL_TRANSPORT_EVENT_CONNECT;
                        }else {
                            transport->event &= ~WL_TRANSPORT_EVENT_WRITE;
                            transport_event = WL_TRANSPORT_EVENT_WRITE;
                        }
                    }

                    wl_mutex_release(&s_transport_global.mutex);


                    if (transport_event) {
                        wl_transport_message transport_message;
                        wl_task_message message;
                        memset(&transport_message, 0, sizeof(wl_transport_message));
                        memset(&message, 0, sizeof(wl_task_message));
                        transport_message.event = transport_event;
                        transport_message.socket = i + START_OF_SOCKFD;
                        message.type = WL_TASK_MSG_SOCKET;
                        memcpy(&message.data, &transport_message, sizeof(wl_transport_message));
                        message.datalen = sizeof(wl_transport_message);
                        wl_send_task_message(transport->taskid, &message);
                    }
                }               
            }
            
        }else {
            ql_rtos_task_sleep_ms(100);
        }
    }

    ql_rtos_task_delete(s_transport_global.thread);
    wl_mutex_delete(&s_transport_global.mutex);
}


wl_bool wl_transport_set_socket_event(WL_SOCKET socket, wl_int32 event) {
    if (CHECK_SOCKET(socket)) {
        wl_transport* transport = wl_null;
        wl_mutex_acquire(&s_transport_global.mutex);
        transport = &s_transport_global.transports[socket - START_OF_SOCKFD];
        if (transport->inuse && WL_INVALID_SOCKET != transport->fd) {
            transport->event = event;
            wl_transport_send_cmd(transport->fd, WL_TRANSPORT_SET_EVENT_CMD, event);
        }
        wl_mutex_release(&s_transport_global.mutex);
        return wl_true;
    } else {
        return wl_false;
    }
}

WL_SOCKET_DESCRIPTOR wl_transport_get_socket_descriptor(WL_SOCKET socket) {
    WL_SOCKET_DESCRIPTOR sd = WL_INVALID_SOCKET;
    if (CHECK_SOCKET(socket)) {
        wl_transport* transport = wl_null;
        wl_mutex_acquire(&s_transport_global.mutex);
        transport = &s_transport_global.transports[socket - START_OF_SOCKFD];
        if (transport->inuse && WL_INVALID_SOCKET != transport->fd) {
            sd = transport->fd;
        }
        wl_mutex_release(&s_transport_global.mutex);
    }
    return sd;
}

wl_int32 wl_transport_get_socket_taskid(WL_SOCKET socket) {
    if (CHECK_SOCKET(socket)) {
        return s_transport_global.transports[socket - START_OF_SOCKFD].taskid;
    }
    return -1;
}

wl_int32 wl_transport_set_socket_callback(WL_SOCKET socket, wl_sock_callback callback, void* context) {
    if (CHECK_SOCKET(socket)) {
        wl_transport* transport = wl_null;
        wl_mutex_acquire(&s_transport_global.mutex);
        transport = &s_transport_global.transports[socket - START_OF_SOCKFD];
        // WLLOGT("socket %d callback %p context %p inuse %d fd %d",
        //        socket,
        //        callback,
        //        context,
        //        transport->inuse,
        //        transport->fd);
        if (transport->inuse && WL_INVALID_SOCKET != transport->fd) {
            transport->callback = callback;
            transport->context  = context;
        }
        wl_mutex_release(&s_transport_global.mutex);
        return 0;
    } else {
        WLLOGW("socket %d invalid", socket);
        return -1;
    }
}

wl_int32 wl_transport_init(void) {
    struct sockaddr_in addr_serv;

    addr_serv.sin_family = AF_INET;
    addr_serv.sin_port = htons(COMMAND_UDP_PORT);
    addr_serv.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

    WLLOGD("wl_transport_init");
    if (!s_transport_global.initialized) {
        s_transport_global.exit = wl_false;

        if (0 != wl_mutex_create("wlsdk_transport", &s_transport_global.mutex)) {
            WLLOGW("wl_mutex_create fail");
            return -1;
        }

        // 创建一个命令用的udp socket，用来通知select的线程进行一些操作
        s_transport_global.cmdUdpFd = socket(AF_INET, SOCK_DGRAM, 0);
        if (s_transport_global.cmdUdpFd < 0) {
            WLLOGW("command udp socket create failed");
            wl_mutex_delete(&s_transport_global.mutex);
            return -1;
        }

        if (bind(s_transport_global.cmdUdpFd, (struct sockaddr *)&addr_serv, sizeof(addr_serv)) < 0) {
            WLLOGW("command udp socket binding failed");
            close(s_transport_global.cmdUdpFd);
            wl_mutex_delete(&s_transport_global.mutex);
            return -1;
        }

        //创建线程
        if (0 != ql_rtos_task_create(&s_transport_global.thread, 8 * 1024, 100, "wl_transport", transport_main_thread, wl_null)) {
            wl_mutex_delete(&s_transport_global.mutex);
        }
        s_transport_global.initialized = wl_true;
        WLLOGT("wl_transport_init success");
    }
    return 0;
}

void wl_transport_end(void) {
    WLLOGD("wl_transport_end");
    if (s_transport_global.initialized) {
        s_transport_global.exit  = wl_true;
        s_transport_global.count = 0;
        wl_transport_send_cmd(0, WL_TRANSPORT_END_CMD, 0);
        s_transport_global.initialized = wl_false;
    }
}

WL_SOCKET wl_transport_add_socket(WL_SOCKET_DESCRIPTOR sd, wl_int32 type, wl_int32 taskid) {
    wl_int32 index = 0;

    wl_mutex_acquire(&s_transport_global.mutex);
    for (index = 0; index < MAX_SOCK_NUM; index++) {
        if (!s_transport_global.transports[index].inuse) {
            WL_SOCKET     socket    = index + START_OF_SOCKFD;
            wl_transport* transport = &s_transport_global.transports[index];
            memset(transport, 0, sizeof(wl_transport));
            transport->fd     = sd;
            transport->taskid = taskid;
            transport->inuse  = wl_true;
            transport->type   = type;
            transport->event  = WL_TRANSPORT_EVENT_NONE;
            s_transport_global.count++;
            wl_mutex_release(&s_transport_global.mutex);

            // 通知select 线程， 增加一个socket
            wl_transport_send_cmd(transport->fd, WL_TRANSPORT_ADD_CMD, 0);
            return socket;
        }
    }
    wl_mutex_release(&s_transport_global.mutex);
    return WL_INVALID_SOCKET;
}

wl_int32 wl_transport_delete_socket(WL_SOCKET socket) {
    if (CHECK_SOCKET(socket)) {
        wl_transport* transport = wl_null;
        WL_SOCKET_DESCRIPTOR fd;
        wl_mutex_acquire(&s_transport_global.mutex);
        WLLOGW("WL_SOCKET:%d  %d", socket, socket - START_OF_SOCKFD);
        transport = &s_transport_global.transports[socket - START_OF_SOCKFD];
        if (transport->inuse && WL_INVALID_SOCKET != transport->fd) {
            s_transport_global.count--;
            transport->inuse    = wl_false;
            transport->taskid   = 0;
            transport->fd       = WL_INVALID_SOCKET;
            transport->callback = wl_null;
            transport->context  = wl_null;

            //通知select 线程删除一个socket
            fd = transport->fd;
            wl_transport_send_cmd(fd, WL_TRANSPORT_DEL_CMD, 0);
        }
        wl_mutex_release(&s_transport_global.mutex);
        return 0;
    } else {
        return -1;
    }
}

wl_int32 wl_transport_process_message(wl_task_message* message) {
    wl_transport_message transport_message;
    wl_sock_callback     callback = wl_null;
    void*                context  = wl_null;

    if (wl_null == message || sizeof(wl_transport_message) != message->datalen) {
        WLLOGW("message is null");
        return -1;
    }
    memcpy(&transport_message, &(message->data), message->datalen);
    if (CHECK_SOCKET(transport_message.socket)) {
        wl_transport* transport = wl_null;
        wl_mutex_acquire(&s_transport_global.mutex);
        transport = &s_transport_global.transports[transport_message.socket - START_OF_SOCKFD];
        callback  = transport->callback;
        context   = transport->context;
        wl_mutex_release(&s_transport_global.mutex);

        if (callback) {
            WL_SOCK_EVENT socket_event = translate_transport_event_to_socket_event(transport_message.event);
            callback(transport_message.socket, socket_event, context);
        } else {
            WLLOGW("socket: %d callback is null", transport_message.socket);
        }
    } else {
        WLLOGW("invalid socket %d", transport_message.socket);
    }

    return 0;
}

#ifdef __cplusplus
}
#endif /* __cplusplus */
